package jxmap

import (
	"sync"
	"sync/atomic"
)

// Array is a high-performance concurrent-safe generic ordered array.
type Array[T any] struct {
	shards     []*arrayShard[T]
	shardCount int
	resizeLock sync.Mutex
	length     int64
}

type arrayShard[T any] struct {
	mu    sync.RWMutex
	items []T
}

// New creates a new concurrent array with the specified shard count.
func NewArray[T any](shardCount ...int) *Array[T] {
	shardSize := 16
	if len(shardCount) > 0 {
		shardSize = shardCount[0]
	}

	shards := make([]*arrayShard[T], shardSize)
	for i := range shards {
		shards[i] = &arrayShard[T]{
			items: make([]T, 0, 32),
		}
	}

	return &Array[T]{
		shards:     shards,
		shardCount: shardSize,
	}
}

// Len returns the current length of the array (lock-free).
func (a *Array[T]) Len() int {
	return int(atomic.LoadInt64(&a.length))
}

// Append adds an element to the end of the array (sharded lock).
func (a *Array[T]) Append(value T) int {
	index := atomic.LoadInt64(&a.length)
	shardIdx := int(index) % a.shardCount
	shard := a.shards[shardIdx]

	shard.mu.Lock()
	shard.items = append(shard.items, value)
	shard.mu.Unlock()

	atomic.AddInt64(&a.length, 1)
	return int(index)
}

// Get retrieves an element at the specified index (read lock).
func (a *Array[T]) Get(index int) (T, bool) {
	var zero T
	if index < 0 || index >= int(atomic.LoadInt64(&a.length)) {
		return zero, false
	}

	shardIdx := index % a.shardCount
	shard := a.shards[shardIdx]

	shard.mu.RLock()
	defer shard.mu.RUnlock()

	localIdx := index / a.shardCount
	if localIdx >= len(shard.items) {
		return zero, false
	}

	return shard.items[localIdx], true
}

// Set updates the element at the specified index.
func (a *Array[T]) Set(index int, value T) bool {
	if index < 0 || index >= int(atomic.LoadInt64(&a.length)) {
		return false
	}

	shardIdx := index % a.shardCount
	shard := a.shards[shardIdx]

	shard.mu.Lock()
	defer shard.mu.Unlock()

	localIdx := index / a.shardCount
	if localIdx >= len(shard.items) {
		return false
	}

	shard.items[localIdx] = value
	return true
}

// Range iterates over the array elements safely (snapshot-based).
func (a *Array[T]) Range(f func(index int, value T) bool) {
	length := int(atomic.LoadInt64(&a.length))

	for i := 0; i < length; i++ {
		shardIdx := i % a.shardCount
		shard := a.shards[shardIdx]
		localIdx := i / a.shardCount

		shard.mu.RLock()
		if localIdx < len(shard.items) {
			if !f(i, shard.items[localIdx]) {
				shard.mu.RUnlock()
				break
			}
		}
		shard.mu.RUnlock()
	}
}

// Delete removes the element at the specified index.
func (a *Array[T]) Delete(index int) bool {
	if index < 0 || index >= int(atomic.LoadInt64(&a.length)) {
		return false
	}

	a.resizeLock.Lock()
	defer a.resizeLock.Unlock()

	shardIdx := index % a.shardCount
	shard := a.shards[shardIdx]

	shard.mu.Lock()
	defer shard.mu.Unlock()

	localIdx := index / a.shardCount
	if localIdx >= len(shard.items) {
		return false
	}

	// Remove element and check capacity
	shard.items = append(shard.items[:localIdx], shard.items[localIdx+1:]...)
	atomic.AddInt64(&a.length, -1)

	// Memory leak fix: Release unused capacity [[2]][[6]]
	if len(shard.items) < cap(shard.items)/4 && cap(shard.items) > 32 {
		newItems := make([]T, len(shard.items))
		copy(newItems, shard.items)
		shard.items = newItems
	}

	return true
}

// Insert inserts an element at the specified index.
func (a *Array[T]) Insert(index int, value T) bool {
	if index < 0 || index > int(atomic.LoadInt64(&a.length)) {
		return false
	}

	a.resizeLock.Lock()
	defer a.resizeLock.Unlock()

	shardIdx := index % a.shardCount
	shard := a.shards[shardIdx]

	shard.mu.Lock()
	defer shard.mu.Unlock()

	localIdx := index / a.shardCount
	if localIdx > len(shard.items) {
		return false
	}

	// Insert element and check capacity
	var zero T
	shard.items = append(shard.items, zero)
	copy(shard.items[localIdx+1:], shard.items[localIdx:])
	shard.items[localIdx] = value
	atomic.AddInt64(&a.length, 1)

	// Memory leak fix: Release unused capacity [[2]][[6]]
	if len(shard.items) < cap(shard.items)/4 && cap(shard.items) > 32 {
		newItems := make([]T, len(shard.items))
		copy(newItems, shard.items)
		shard.items = newItems
	}

	return true
}

// ToSlice returns a copy of the array as a slice.
func (a *Array[T]) ToSlice() []T {
	length := int(atomic.LoadInt64(&a.length))
	result := make([]T, 0, length)

	a.Range(func(_ int, value T) bool {
		result = append(result, value)
		return true
	})

	return result
}
